package parser

import (
	"encoding/xml"
	"fmt"
	"io"
	"math/rand"
	"strings"
	"time"

	"gitee.com/tonyjia/gb28181/pkg/model"
	"github.com/beevik/etree"
	"github.com/pkg/errors"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/cast"
	"golang.org/x/text/encoding/simplifiedchinese"
	"golang.org/x/text/transform"
)

type QueryType string
type ControlType string

type WithKeyValue func(element *etree.Element)

const (
	DeviceConfig  ControlType = "DeviceConfig"
	DeviceControl ControlType = "DeviceControl"
)

const (
	DeviceStatusCmdType   QueryType = "DeviceStatus"
	CatalogCmdType        QueryType = "Catalog"
	KeepaliveCmdType      QueryType = "Keepalive"
	DeviceInfoCmdType     QueryType = "DeviceInfo"
	RecordInfoCmdType     QueryType = "RecordInfo"
	AlarmCmdType          QueryType = "Alarm"
	ConfigDownloadCmdType QueryType = "ConfigDownload"
	PresetQueryCmdType    QueryType = "PresetQuery"
	MobilePositionCmdType QueryType = "MobilePosition"
)

// CreateQueryXML create catalog query request xml of sip message and return
func CreateQueryXML(cmd QueryType, deviceId string, kvs ...WithKeyValue) (string, error) {
	document := etree.NewDocument()
	document.CreateProcInst("xml", "version=\"1.0\" encoding=\"GB2312\"")
	query := document.CreateElement("Query")
	query.CreateElement("CmdType").CreateText(string(cmd))
	query.CreateElement("SN").CreateText(getSN())
	query.CreateElement("DeviceID").CreateText(deviceId)

	for _, kv := range kvs {
		kv(query)
	}

	document.Indent(2)
	body, err := document.WriteToString()
	if err != nil {
		log.Error(err)
		return "", errors.Wrap(err, "encoding catalog query request xml fail")
	}
	return body, nil
}

// CreateNotifyXML create catalog query request xml of sip message and return
func CreateNotifyXML(cmd QueryType, deviceId string, kvs ...WithKeyValue) (string, error) {
	document := etree.NewDocument()
	document.CreateProcInst("xml", "version=\"1.0\" encoding=\"GB2312\"")
	query := document.CreateElement("Notify")
	document.CreateElement("CmdType").CreateText(string(cmd))
	document.CreateElement("SN").CreateText(getSN())
	document.CreateElement("DeviceID").CreateText(deviceId)

	for _, kv := range kvs {
		kv(query)
	}

	document.Indent(2)
	body, err := document.WriteToString()
	if err != nil {
		log.Error(err)
		return "", errors.Wrap(err, "encoding catalog query request xml fail")
	}
	return body, nil
}

// CreateCatalogNotifyXML create catalog query request xml of sip message and return
func CreateCatalogNotifyXML(cmd QueryType, channels []model.Channel, deviceId string, kvs ...WithKeyValue) (string, error) {
	document := etree.NewDocument()
	document.CreateProcInst("xml", "version=\"1.0\" encoding=\"GB2312\"")
	query := document.CreateElement("Notify")
	document.CreateElement("CmdType").CreateText(string(cmd))
	document.CreateElement("SN").CreateText(getSN())
	document.CreateElement("DeviceID").CreateText(deviceId)
	deviceListTag := fmt.Sprintf("DeviceList Num=%d", len(channels))

	for _, channel := range channels {
		channelStr, err := xml.Marshal(channel)
		if err != nil {
			log.Errorf("failed to marshal channel to xml string, Err: %v", err)
			continue
		}
		document.CreateElement(deviceListTag).CreateElement("Item").CreateText(string(channelStr))
	}

	for _, kv := range kvs {
		kv(query)
	}

	document.Indent(2)
	body, err := document.WriteToString()
	if err != nil {
		log.Error(err)
		return "", errors.Wrap(err, "encoding catalog query request xml fail")
	}
	return body, nil
}

// CreateDeviceStatusXML create device status query request xml of sip message and return
func CreateDeviceStatusXML(cmd QueryType, deviceId string, status string, kvs ...WithKeyValue) (string, error) {
	document := etree.NewDocument()
	document.CreateProcInst("xml", "version=\"1.0\" encoding=\"GB2312\"")
	query := document.CreateElement("Notify")
	query.CreateElement("CmdType").CreateText(string(cmd))
	query.CreateElement("SN").CreateText(getSN())
	query.CreateElement("DeviceID").CreateText(deviceId)
	query.CreateElement("Status").CreateText(status)
	for _, kv := range kvs {
		kv(query)
	}

	document.Indent(2)
	body, err := document.WriteToString()
	if err != nil {
		log.Error(err)
		return "", errors.Wrap(err, "encoding catalog query request xml fail")
	}
	return body, nil
}

// CreateSubscribeXML create subscribe request xml of sip message and return
func CreateSubscribeXML(cmd QueryType, deviceId string, kvs ...WithKeyValue) (string, error) {
	document := etree.NewDocument()
	document.CreateProcInst("xml", "version=\"1.0\" encoding=\"GB2312\"")
	query := document.CreateElement("Subscribe")
	query.CreateElement("CmdType").CreateText(string(cmd))
	query.CreateElement("SN").CreateText(getSN())
	query.CreateElement("DeviceID").CreateText(deviceId)

	for _, kv := range kvs {
		kv(query)
	}

	document.Indent(2)
	body, err := document.WriteToString()
	if err != nil {
		log.Error(err)
		return "", errors.Wrap(err, "encoding catalog query request xml fail")
	}
	return body, nil
}

func CreateCatalogResponseXml(cmd QueryType, deviceId string, SN string, channels []model.Channel, kvs ...WithKeyValue) (string, error) {
	document := etree.NewDocument()
	document.CreateProcInst("xml", "version=\"1.0\" encoding=\"GB2312\"")
	// 创建设备列表元素
	query := document.CreateElement("Response")
	query.CreateElement("CmdType").CreateText(string(cmd))
	query.CreateElement("SN").CreateText(SN)
	query.CreateElement("DeviceID").CreateText(deviceId)
	query.CreateElement("SumNum").CreateText(fmt.Sprintf("%d", len(channels)))
	deviceList := query.CreateElement("DeviceList")
	deviceList.CreateAttr("Num", fmt.Sprintf("%d", len(channels)))
	// 遍历设备信息并创建 XML 元素
	for _, channel := range channels {
		item := deviceList.CreateElement("Item")
		item.CreateElement("DeviceID").CreateText(channel.DeviceId)
		item.CreateElement("Name").CreateText(channel.Name)
		item.CreateElement("Manufacturer").CreateText(channel.Manufacturer)
		item.CreateElement("Model").CreateText(channel.Model)
		item.CreateElement("Owner").CreateText(channel.Owner)
		item.CreateElement("CivilCode").CreateText(channel.CivilCode)
		//item.CreateElement("Block").CreateText(channel.)
		item.CreateElement("Address").CreateText(channel.Address)
		item.CreateElement("Parental").CreateText(channel.Parental)
		item.CreateElement("ParentID").CreateText(channel.ParentID)
		item.CreateElement("SafetyWay").CreateText(channel.SafetyWay)
		item.CreateElement("RegisterWay").CreateText(channel.RegisterWay)
		//item.CreateElement("CertNum").CreateText(channel.CertNum)
		//item.CreateElement("Certifiable").CreateText(fmt.Sprintf("%d", channel.Certifiable))
		//item.CreateElement("ErrCode").CreateText(fmt.Sprintf("%d", channel.ErrCode))
		//item.CreateElement("EndTime").CreateText(channel.EndTime.Format("2006-01-02T15:04:05"))
		item.CreateElement("Secrecy").CreateText(channel.Secrecy)
		//item.CreateElement("IPAddress").CreateText(channel.IPAddress)
		//item.CreateElement("Port").CreateText(fmt.Sprintf("%d", channel.Port))
		//item.CreateElement("Password").CreateText(channel.Password)
		item.CreateElement("Status").CreateText(channel.Status)
		//item.CreateElement("Longitude").CreateText(fmt.Sprintf("%.1f", channel.Longitude))
		//item.CreateElement("Latitude").CreateText(fmt.Sprintf("%.1f", channel.Latitude))
	}
	for _, kv := range kvs {
		kv(query)
	}

	document.Indent(2)
	body, err := document.WriteToString()
	if err != nil {
		log.Error(err)
		return "", errors.Wrap(err, "encoding catalog query response xml fail")
	}
	return body, nil
}

func CreateDeviceResponseXml(cmd QueryType, SN string, device model.Device, kvs ...WithKeyValue) (string, error) {
	document := etree.NewDocument()
	document.CreateProcInst("xml", "version=\"1.0\" encoding=\"GB2312\"")
	query := document.CreateElement("Response")
	query.CreateElement("CmdType").CreateText(string(cmd))
	query.CreateElement("SN").CreateText(SN)
	query.CreateElement("DeviceID").CreateText(device.DeviceID)

	for _, kv := range kvs {
		kv(query)
	}

	document.Indent(2)
	body, err := document.WriteToString()
	if err != nil {
		log.Error(err)
		return "", errors.Wrap(err, "encoding catalog query request xml fail")
	}
	return body, nil
}

// WithCustomKV create 'k' item of xml by 'v'
func WithCustomKV(k, v string) WithKeyValue {
	return func(element *etree.Element) {
		element.CreateElement(k).CreateText(v)
	}
}

func getSN() string {
	rand.Seed(time.Now().UnixMilli())
	return cast.ToString(rand.Intn(10) * 9876)
}

// GetCmdTypeFromXML 根据body获取XML配置文件中的根元素
func GetCmdTypeFromXML(body string) (key string, err error) {
	decoder := xml.NewDecoder(strings.NewReader(body))
	decoder.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
		if charset == "GB2312" {
			return transform.NewReader(input, simplifiedchinese.GB18030.NewDecoder()), nil
		}
		return input, nil
	}
	var (
		isRoot, isCmdType = false, false
		root, cmdType     string
	)

re:
	for t, err := decoder.Token(); err == nil || err == io.EOF; t, err = decoder.Token() {
		switch token := t.(type) {
		case xml.StartElement:
			if !isRoot {
				root = token.Name.Local
				isRoot = true
			}
			if token.Name.Local == "CmdType" {
				isCmdType = true
			}
		case xml.CharData:
			if isCmdType {
				cmdType = string(token)
				break re
			}
		default:
		}
	}

	key = fmt.Sprintf("%s:%s", root, cmdType)
	return
}
